package com.curluin.model;

import java.util.HashMap;
import java.util.Vector;

import android.util.SparseIntArray;

import com.curluin.controll.Controller;
import com.curluin.math.MyVector;
import com.curluin.scene.Node;
import com.curluin.scene.TriangleMesh;

public abstract class Moveable extends ModelObject{
	
	static final String LOG_TAG = "Moveable";

	/**
	 * Vector which represents velocity. The direction of the vector represents
	 * the moving direction, the length the speed in pixels per milliseconds.
	 */
	protected MyVector velocity;

	/**
	 * Summed friction of ground. Computed automatically via GridFields.
	 */
	protected float summedFriction;
	
	protected SparseIntArray possibleGamephases= new SparseIntArray();
	
	
	/**
	 * Weight of Object
	 */
	protected float weight=30;
	/**
	 * all Collisions occured
	 */
	protected HashMap<ModelObject, MyVector> collisions=new HashMap<ModelObject, MyVector>();

	/**
	 * GridFields which are occupied by this Object
	 */
	public Vector<GridField> occGridFields=new Vector<GridField>(); 

	/**
	 * act ground beneath Object
	 */
	//protected Ground actGround;
	
	protected int gridFieldsWithHighestGroundsCounter = 0;
	
	/**
	 *  Height of highest ground below object
	 */
	protected float floor = Float.NEGATIVE_INFINITY,ceiling = Float.POSITIVE_INFINITY;
	protected float rightBarrier = Float.POSITIVE_INFINITY, leftBarrier = Float.NEGATIVE_INFINITY;
	protected float frontBarrier = Float.NEGATIVE_INFINITY, backBarrier = Float.POSITIVE_INFINITY;

	/**
	 * previous Ground
	 */
	//private Ground oldGround;
	
	public boolean gliding = false;

	protected MyVector acceleration=MyVector.makeVector3(0, Controller.GRAVITY_ACCELERATION, 0);

	/**
	 * Constructor
	 * @param position: initial position in level
	 * @param velocity: initial velocity
	 */
	public Moveable(MyVector position, MyVector velocity, int consitency){
		super(position, consitency);
		this.velocity = velocity == null ? MyVector.makeVector3(0, 0, 0) : velocity;
	}
	
	public Moveable(TriangleMesh mesh, MyVector position , MyVector velocity, int consitency){
		super(mesh, position, consitency);
		this.velocity=velocity;
	}

	/**
	 * Computes next position based on the old position, the current velocity
	 * and the passedTime.
	 * @param passedTime
	 * @return next position that the object wants to move to
	 */
	public MyVector computeNextPosition(float passedTime) {
		
		// Next position of moveable object
		MyVector newPosition = position.add(velocity.multiply(passedTime));
		
		// Can't fall deeper than floor (= highest ground) or rise higher than ceiling
		float minHeight= floor+ getSize().get(1)/2;
		float myHeight= newPosition.get(1);
		
//		if(minHeight>myHeight) Log.e(LOG_TAG, "floor:"+minHeight+" me:"+myHeight);
		newPosition.set(1, Math.min(ceiling - getSize().get(1)/2, Math.max(minHeight, myHeight)));

		return newPosition;
	}
	/**
	 * Recomputes velocity of this Object.
	 */
	public MyVector recomputeVelocity(float passedTime){
		//TODO replace fixed values by some kind of physic model	

		// Reduce velocity due to ground-friction (when no ground below -> no friction -> no reduction) 
		MyVector velDecrease = velocity.multiply(passedTime*getFriction());
//		Log.e(LOG_TAG, "Friction: " + getFriction());
		MyVector accel = gliding? MyVector.makeVector3(0, 0, 0): acceleration;
		MyVector newVelocity = velocity.add(accel).subtract(velDecrease);
		return newVelocity;
	}
	/**
	 * Adds a GridField to the occupied GridFields of this object, updates Ground.
	 * @param gf
	 */
	public void addGridField(GridField gf){
		
		float epsilon = 0.001f, oldFloor;
		
		occGridFields.add(gf);

		Float gfGroundHeight;
		
		for(Ground gfGround : gf.getGrounds()){
			
			
			gfGroundHeight = gfGround.getCenter().get(1) + gfGround.getSize().get(1)/2;
			if(gfGround.getGamePhase()==Controller.GAMEPHASE_RUNWAY){
				floor=gfGroundHeight;
				possibleGamephases.clear();
				possibleGamephases.put(Controller.GAMEPHASE_RUNWAY, 1);
				break;
			}
			// If height of ground of GridField is similar to heights of highest grounds below object: increase counter
			if(Math.abs(gfGroundHeight - floor) < epsilon){
				gridFieldsWithHighestGroundsCounter++;
				updateSummedFriction(gf.computeFriction(gfGround));
				int gamephase=gfGround.getGamePhase();
				if(possibleGamephases.indexOfKey(gamephase)>=0){
					int nextCount = possibleGamephases.get(gamephase)+1;
					possibleGamephases.put(gamephase,nextCount);
				}
				//Log.e(LOG_TAG, "Same Height, add To possGamephases: "+possibleGamephases.size());
			}else{
				if(gfGround.getConsitency()==ModelObject.CONSITENCY_FLUID) continue;
				// ...when it's higher...
				if (gfGroundHeight > floor){
					// ...and when it's still below the object: it's the only highest ground below the object
					if((position.get(1) - getSize().get(1)/2) + epsilon > gfGroundHeight){
						
						floor = gfGroundHeight;
						summedFriction = gf.computeFriction(gfGround);
						possibleGamephases.clear();
						possibleGamephases.put(gfGround.getGamePhase(), 1);
						gridFieldsWithHighestGroundsCounter = 1;
					
					}else{
						// ...otherwise: it's either far above the object and hence has no impact: do nothing
						if(gfGroundHeight - gfGround.getSize().get(1) + epsilon < position.get(1) + getSize().get(1)/2){
							// ... or it's a collision
							addCollision(gfGround, this.getCenter());

						}
					}
				}
			}
		}
		
		if (Math.abs(position.get(1) - getSize().get(1)/2 - floor) < epsilon) gliding = true; 
		//Log.e(LOG_TAG, "Num possible Gamephases "+possibleGamephases.size());
	}

//	/**
//	 * ONLY PURPOSE: called by addGridField. After removeFromGridFields(sets ground=null) was called. 
//	 * The new Ground is only set if all GridFields have same Ground..
//	 */
//	private void updateGround(Ground gfGround){
//		//		Log.w("Moveable","setGround? "+gfGround+" actGround: "+this.actGround+" oldGround: "+oldGround);
//		if(gfGround==null)return;
//		if(this.actGround==null) this.actGround =gfGround;
//		else if(!actGround.equals(gfGround)) actGround=oldGround;
//	
//		//		Log.w("Moveable","newGround: "+actGround);
//	}
	
	/**ONLY FOR GRIDFIELDS. Adds by GridField computed friction to summedFriction.  
	 * @param friction
	 */
	public void updateSummedFriction(float friction){
		// Update sum of Friction
		summedFriction += friction;
	}
	/**
	 * removes this Object from all GridFields it was occupying
	 */
	public void removeFromGridFields(){
//		oldGround=actGround;
//		actGround=null;
		for(GridField gf:occGridFields){
			gf.removeOccupant(this);
		}
		occGridFields.clear();
		occGridFields.trimToSize();
		summedFriction = 0;
		possibleGamephases.clear();
		gridFieldsWithHighestGroundsCounter = 0;
		leftBarrier = frontBarrier = floor = Float.NEGATIVE_INFINITY;
		rightBarrier = backBarrier = ceiling = Float.POSITIVE_INFINITY;
		gliding = false;
	}



	/**
	 * Sets Drift in the x-direction. Will be added to velocity in next recomputation.
	 */
	public void setDrift(float drift){
		float maxDrift = Math.abs(velocity.get(2));
		float newDrift = velocity.get(0)+drift;
		//ensure Drift is not larger than velocity in z direction
		newDrift = (Math.abs(newDrift)<maxDrift)? newDrift : (newDrift<0)? -maxDrift :maxDrift;
		this.velocity.set(0, newDrift);
	}

	/**
	 * @param obstacle with wich the object collided
	 * @param collisionLocation where the collision occured
	 */
	public void addCollision(ModelObject obstacle, MyVector collisionLocation) {
		collisions.put(obstacle, collisionLocation);
	}

	/**
	 * @return true if this Object was involved in a collision
	 */
	public boolean collisionOccured(){
		return !collisions.isEmpty();
	}

	public HashMap<ModelObject,MyVector> getCollisions(){
		return collisions;
	}

	public void setVelocity(MyVector velocity){
		this.velocity = velocity;
	}
	public MyVector getVelocity(){
		return MyVector.makeCopy(velocity);
	}
	
	/**
	 * @return Friction mean of underlying ground (computed via GridFields) 
	 */
	protected float getFriction(){
		return !gliding || gridFieldsWithHighestGroundsCounter == 0 ? 0 : summedFriction/gridFieldsWithHighestGroundsCounter;
	}
	

	public int getGamephase(){
//		Log.w(LOG_TAG, "HighestGroundCounter: "+gridFieldsWithHighestGroundsCounter+"summedGamePhase: "+summedGamephase);
		int max = 0;
		int gamephase=Controller.GAMEPHASE_UNDEFINED;
		int c, key;
		for(int i=0; i<possibleGamephases.size(); i++){
			key= possibleGamephases.keyAt(i);
			c=possibleGamephases.get(key);
			if(c>max){
				max=c;
				gamephase=key;
			}
		}
		return gamephase;
	}

	/**
	 * Sets rotation of ModelObject
	 * @param rotation
	 */
	public void setRotation(MyVector rotation){
		//TODO rotate scandata of ApproximateShape
		
		this.node.rotate(rotation, Node.RESET);
	}
	
	public void setVelocity(int index, float val){
		this.velocity.set(index, val);
	}

	public Vector<MyVector> translateScanData(MyVector posShift) {
		return this.shape.translateScanData( posShift);
//		return node.translateScanData(posShift);
		
	}

	public void removeCollisions() {
		collisions.clear();
	}
}
